iT邦幫忙

2024 iThome 鐵人賽

DAY 26
0
Mobile Development

Flutter基礎入門系列 第 26

【Day 26】建立客製化的時刻表吧!

  • 分享至 

  • xImage
  •  

現在Todo頁面的雛型已經完成了,那麼今天就來做TimeTable吧!
關於此應用程式的各個頁面的用途可以看看筆者的文章:【Day 09】設計的第一步驟:介面規劃與功能設定


時刻表package: kalender

筆者將在此使用kalender package ,作者在此網站有個線上的程式範例,供大家玩玩看。此package已經建立好時刻表的主要物件,並且能夠輕鬆客製化修改他們的外觀及功能。

它所擁有的功能包括:

  1. 多種時刻表顯示方式
    • 如以日、週、月為單位顯示,甚至能夠客製化顯示方式
  2. 能夠以拖拉的方式變更行程的安排時間與時長
  3. 客製化Event Handling
  4. 客製化行程物件
  5. 含有Builders功能,能自己建立不同物件的新功能及零件
  6. 能自訂顯示行程所需要的演算法

下載使用的方式與其他package相同,在shell中輸入flutter pub add kalender,便能夠自動加入pubspec.yaml的dependencies欄位。

關於此package的簡單使用說明,可參考作者的說明:Usage,但因說明似乎並未更新至最新版本,有些部份仍須修改,因此筆者也另外參考了作者的範例檔案example

以下為筆者做部份修改後用於顯示時刻表的表格本身的程式碼:

import 'dart:io' show Platform;
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import 'package:kalender/kalender.dart';
import 'package:schedrag/data/models/child_blocks.dart';

class TimetablePage extends StatefulWidget {
  const TimetablePage({super.key});

  @override
  State<TimetablePage> createState() => _TimetablePageState();
}

class _TimetablePageState extends State<TimetablePage> {
  final CalendarController<TimeBlock> controller = CalendarController(
    calendarDateTimeRange: DateTimeRange(
      start: DateTime(DateTime.now().year - 1),
      end: DateTime(DateTime.now().year + 1),
    ),
  );
  final CalendarEventsController<TimeBlock> eventController =
      CalendarEventsController<TimeBlock>();

  late ViewConfiguration currentConfiguration = viewConfigurations[0];
  List<ViewConfiguration> viewConfigurations = [
    CustomMultiDayConfiguration(
      name: 'Day',
      numberOfDays: 1,
      startHour: 6,
      endHour: 18,
    ),
    CustomMultiDayConfiguration(
      name: 'Custom',
      numberOfDays: 1,
    ),
    WeekConfiguration(),
    WorkWeekConfiguration(),
    MonthConfiguration(),
    ScheduleConfiguration(),
    MultiWeekConfiguration(
      numberOfWeeks: 3,
    ),
  ];

  @override
  void initState() {
    super.initState();
    DateTime now = DateTime.now();
    eventController.addEvents([
      CalendarEvent(
        dateTimeRange: DateTimeRange(
          start: now,
          end: now.add(const Duration(hours: 1)),
        ),
        eventData: TimeBlock.name('Event 1'),
      ),
      CalendarEvent(
        dateTimeRange: DateTimeRange(
          start: now.add(const Duration(hours: 2)),
          end: now.add(const Duration(hours: 5)),
        ),
        eventData: TimeBlock.name('Event 2'),
      ),
      CalendarEvent(
        dateTimeRange: DateTimeRange(
          start: DateTime(now.year, now.month, now.day),
          end: DateTime(now.year, now.month, now.day)
              .add(const Duration(days: 2)),
        ),
        eventData: TimeBlock.name('Event 3'),
      ),
    ]);
  }

  @override
  Widget build(BuildContext context) {
    final calendar = CalendarView<TimeBlock>(
      controller: controller,
      eventsController: eventController,
      viewConfiguration: currentConfiguration,
      tileBuilder: _tileBuilder,
      multiDayTileBuilder: _multiDayTileBuilder,
      scheduleTileBuilder: _scheduleTileBuilder,
      components: CalendarComponents(
        calendarHeaderBuilder: _calendarHeader,
      ),
      eventHandlers: CalendarEventHandlers(
        onEventTapped: _onEventTapped,
        onEventChanged: _onEventChanged,
        onCreateEvent: _onCreateEvent,
        onEventCreated: _onEventCreated,
      ),
    );

    return SafeArea(
      child: Scaffold(
        body: calendar,
      ),
    );
  }

  CalendarEvent<TimeBlock> _onCreateEvent(DateTimeRange dateTimeRange) {
    return CalendarEvent(
      dateTimeRange: dateTimeRange,
      eventData: TimeBlock.name('New Event'),
    );
  }

  Future<void> _onEventCreated(CalendarEvent<TimeBlock> event) async {
    // Add the event to the events controller.
    eventController.addEvent(event);

    // Deselect the event.
    eventController.deselectEvent();
  }

  Future<void> _onEventTapped(
    CalendarEvent<TimeBlock> event,
  ) async {
    if (isMobile) {
      eventController.selectedEvent == event
          ? eventController.deselectEvent()
          : eventController.selectEvent(event);
    }
  }

  Future<void> _onEventChanged(
    DateTimeRange initialDateTimeRange,
    CalendarEvent<TimeBlock> event,
  ) async {
    if (isMobile) {
      eventController.deselectEvent();
    }
  }

  Widget _tileBuilder(
    CalendarEvent<TimeBlock> event,
    TileConfiguration configuration,
  ) {
    return Card(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(8),
      ),
      margin: EdgeInsets.zero,
      elevation: configuration.tileType == TileType.ghost ? 0 : 8,
      child: Center(
        child: configuration.tileType != TileType.ghost
            ? Text(event.eventData?.name ?? 'New Event')
            : null,
      ),
    );
  }

  Widget _multiDayTileBuilder(
    CalendarEvent<TimeBlock> event,
    MultiDayTileConfiguration configuration,
  ) {
    return Card(
      margin: const EdgeInsets.symmetric(vertical: 2),
      elevation: configuration.tileType == TileType.selected ? 8 : 0,
      child: Center(
        child: configuration.tileType != TileType.ghost
            ? Text(event.eventData?.name ?? 'New Event')
            : null,
      ),
    );
  }

  Widget _scheduleTileBuilder(CalendarEvent<TimeBlock> event, DateTime date) {
    return DecoratedBox(
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(8),
      ),
      child: Text(event.eventData?.name ?? 'New Event'),
    );
  }

  Widget _calendarHeader(DateTimeRange dateTimeRange) {
    return Row(
      children: [
        DropdownMenu(
          onSelected: (value) {
            if (value == null) return;
            setState(() {
              currentConfiguration = value;
            });
          },
          initialSelection: currentConfiguration,
          dropdownMenuEntries: viewConfigurations
              .map((e) => DropdownMenuEntry(value: e, label: e.name))
              .toList(),
        ),
        IconButton.filledTonal(
          onPressed: controller.animateToPreviousPage,
          icon: const Icon(Icons.navigate_before_rounded),
        ),
        IconButton.filledTonal(
          onPressed: controller.animateToNextPage,
          icon: const Icon(Icons.navigate_next_rounded),
        ),
        IconButton.filledTonal(
          onPressed: () {
            controller.animateToDate(DateTime.now());
          },
          icon: const Icon(Icons.today),
        ),
      ],
    );
  }

  bool get isMobile {
    return kIsWeb ? false : Platform.isAndroid || Platform.isIOS;
  }
}

並且於程式中顯示的畫面如下:
https://ithelp.ithome.com.tw/upload/images/20241010/20169446qRVDJohI4O.png


謝謝閱讀到這裡的讀者~有任何問題或想說的都歡迎留言或email。明天將會繼續做Timetable的設定與調整。


上一篇
【Day 25】日期與時間的輸入:DateTime
下一篇
【Day 27】儲存時刻表的新資料
系列文
Flutter基礎入門30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言